package nyist.media.service.impl;

import nyist.media.entity.BaseEntity;
import nyist.media.entity.OrderBaseEntity;
import nyist.media.repository.BaseRepository;
import nyist.media.service.BaseService;
import nyist.media.utils.Filter;
import nyist.media.utils.Order;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Service - 基类
 */
public class BaseServiceImpl<T extends BaseEntity, ID extends Serializable> implements BaseService<T, ID> {

    protected Logger logger = LogManager.getLogger(MethodHandles.lookup().lookupClass());

    /**
     * baseRepository
     */
    private BaseRepository<T, ID> baseRepository;

    public void setBaseRepository(BaseRepository<T, ID> baseRepository) {
        this.baseRepository = baseRepository;
    }

    @Override
    public T find(ID id) {
        if (id == null) {
            return null;
        }
        return baseRepository.findOne(id);
    }

    @Override
    public List<T> findAll() {
        return baseRepository.findAll();
    }

    @Override
    public List<T> findList(ID... ids) {
        if (ids != null) {
            return Stream.of(ids).map(this::find).filter(Objects::nonNull).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    private Class<T> getTClass() {
        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    @Override
    public List<T> findList(List<Filter> filters, List<Order> orders) {
        return baseRepository.findAll((root, query, cb) -> {
            addRestrictions(root, query, cb, filters);
            addOrders(root, query, cb, orders);
            if (query.getOrderList().isEmpty()) {
                if (OrderBaseEntity.class.isAssignableFrom(getTClass())) {
                    query.orderBy(cb.asc(root.get(OrderBaseEntity.ORDER_PROPERTY_NAME)));
                } else {
                    query.orderBy(cb.desc(root.get(OrderBaseEntity.CREATE_DATE_PROPERTY_NAME)));
                }
            }
            return query.getRestriction();
        });
    }

    /**
     * 排序规则
     *
     * @param root
     * @param criteriaQuery
     * @param criteriaBuilder
     * @param orders
     */
    private void addOrders(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder, List<Order> orders) {
        if (orders == null || orders.isEmpty()) {
            return;
        }
        List<javax.persistence.criteria.Order> orderList = new ArrayList<>();
        if (!criteriaQuery.getOrderList().isEmpty()) {
            orderList.addAll(criteriaQuery.getOrderList());
        }
        for (Order order : orders) {
            if (order.getDirection() == Sort.Direction.ASC) {
                orderList.add(criteriaBuilder.asc(root.get(order.getProperty())));
            } else if (order.getDirection() == Sort.Direction.DESC) {
                orderList.add(criteriaBuilder.desc(root.get(order.getProperty())));
            }
        }
        criteriaQuery.orderBy(orderList);
    }

    /**
     * 添加排序限制条件
     *
     * @param root            当前对象
     * @param criteriaQuery   查询对象
     * @param criteriaBuilder 条件构造对象
     * @param filters         筛选
     */
    private void addRestrictions(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder, List<Filter> filters) {
        if (filters == null || filters.isEmpty()) {
            return;
        }
        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        for (Filter filter : filters) {
            if (filter == null || StringUtils.isEmpty(filter.getProperty())) {
                continue;
            }
            if (filter.getOperator() == Filter.Operator.eq && filter.getValue() != null) {
                if (filter.getIgnoreCase() != null && filter.getIgnoreCase() && filter.getValue() instanceof String) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(criteriaBuilder.lower(root.<String>get(filter.getProperty())), ((String) filter.getValue()).toLowerCase()));
                } else {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(root.get(filter.getProperty()), filter.getValue()));
                }
            } else if (filter.getOperator() == Filter.Operator.ne && filter.getValue() != null) {
                if (filter.getIgnoreCase() != null && filter.getIgnoreCase() && filter.getValue() instanceof String) {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower(root.<String>get(filter.getProperty())), ((String) filter.getValue()).toLowerCase()));
                } else {
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(root.get(filter.getProperty()), filter.getValue()));
                }
            } else if (filter.getOperator() == Filter.Operator.gt && filter.getValue() != null) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.gt(root.<Number>get(filter.getProperty()), (Number) filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.lt && filter.getValue() != null) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt(root.<Number>get(filter.getProperty()), (Number) filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.ge && filter.getValue() != null) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge(root.<Number>get(filter.getProperty()), (Number) filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.le && filter.getValue() != null) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.le(root.<Number>get(filter.getProperty()), (Number) filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.like && filter.getValue() != null && filter.getValue() instanceof String) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(root.<String>get(filter.getProperty()), "%" + filter.getValue() + "%"));
            } else if (filter.getOperator() == Filter.Operator.in && filter.getValue() != null) {
                restrictions = criteriaBuilder.and(restrictions, root.get(filter.getProperty()).in(filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.isNull) {
                restrictions = criteriaBuilder.and(restrictions, root.get(filter.getProperty()).isNull());
            } else if (filter.getOperator() == Filter.Operator.isNotNull) {
                restrictions = criteriaBuilder.and(restrictions, root.get(filter.getProperty()).isNotNull());
            } else if (filter.getOperator() == Filter.Operator.greaterThan && filter.getValue() != null && filter.getValue() instanceof Date) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.greaterThan(root.<Date>get(filter.getProperty()), (Date) filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.lessThan && filter.getValue() != null && filter.getValue() instanceof Date) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lessThan(root.<Date>get(filter.getProperty()), (Date) filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.greaterThanOrEqualTo && filter.getValue() != null && filter.getValue() instanceof Date) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.greaterThanOrEqualTo(root.<Date>get(filter.getProperty()), (Date) filter.getValue()));
            } else if (filter.getOperator() == Filter.Operator.lessThanOrEqualTo && filter.getValue() != null && filter.getValue() instanceof Date) {
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lessThanOrEqualTo(root.<Date>get(filter.getProperty()), (Date) filter.getValue()));
            }
        }
        criteriaQuery.where(restrictions);
    }

    @Override
    public long count() {
        return count(new Filter[]{});
    }

    @Override
    public long count(Filter... filters) {
        if (filters != null) {
            return baseRepository.count((root, query, cb) -> {
                addRestrictions(root, query, cb, Arrays.asList(filters));
                return query.getRestriction();
            });
        } else {
            return baseRepository.count();
        }
    }

    @Override
    public boolean exists(ID id) {
        return baseRepository.exists(id);
    }

    @Override
    public boolean exists(Filter... filters) {
        return count(filters) > 0;
    }

    @Transactional
    @Override
    public void save(T entity) {
        baseRepository.save(entity);
    }

    @Transactional
    @Override
    public T update(T entity) {
        return baseRepository.save(entity);
    }

    @Transactional
    @Override
    public void delete(ID... ids) {
        if (ids != null) {
            for (int i = 0, len = ids.length; i < len; i++) {
                delete(baseRepository.findOne(ids[i]));
            }
        }
    }

    @Transactional
    @Override
    public void delete(T entity) {
        baseRepository.delete(entity);
    }
}